package com.timanetworks.mailbox.common.http;

import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionRequest;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.*;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * Created by wusk on 2017/10/11. OpenStack http 连接服务
 * 注意delete方法不能带数据体，只能url传参
 */
@Component
public class HttpConnectionService {
    private static final Logger logger = LoggerFactory.getLogger(HttpConnectionService.class);

    private static final String METHOD_POST = "POST";
    private static final String METHOD_PATCH = "PATCH";
    private static final String METHOD_PUT = "PUT";

    private static final String ENCODE_CHARSET = "UTF-8";

    // 设置超时时间
    private static final int REQUEST_TIMEOUT = 30 * 1000;
    private static final int REQUEST_SOCKET_TIME = 30 * 1000;

    /**
     * 连接池最大连接数
     */
    private static final int POOL_MAX_TOTAL = 40;

    /**
     * 每个路由保持的最大连接数
     */
    private static final int CONNECTION_PER_ROUTE = 2;

    /**
     * 设置一分钟检查一次失效连接
     */
    private static final long CONNECTION_CHECK_INTERNAL = 60000;

    /**
     * 连接池
     */
    private PoolingHttpClientConnectionManager connections;

    private HttpClientContext context;

    /**
     * 固定请求头
     */
    private Header[] headers;

    /**
     * 最近一次清理时间
     */
    private long recent = 0;

    @PostConstruct
    public void init() {
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", new PlainConnectionSocketFactory())
                .build();

        connections = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connections.setMaxTotal(POOL_MAX_TOTAL);
        connections.setDefaultMaxPerRoute(CONNECTION_PER_ROUTE);
        context = HttpClientContext.create();
        recent = System.currentTimeMillis();

        headers = new Header[3];
        headers[0] = new BasicHeader("Connection", "Keep-Alive");
        headers[1] = new BasicHeader("Accept-Encoding", "gzip,deflate");
        headers[2] = new BasicHeader("User-Agent", "Apache-HttpClient/4.5.2");
    }

    public HttpConnectionService() {
    }

    public CommonHttpResponse get(String host, int port, String uri, Header... headers) throws Exception {
        HttpGet get = new HttpGet(url(host, port, uri));
        return request(host, port, get, headers);
//        return doHttpRequest(get, headers);
    }

    public CommonHttpResponse head(String host, int port, String uri, Header... headers) throws Exception {
        HttpHead head = new HttpHead(url(host, port, uri));
        return request(host, port, head, headers);
//        return doHttpRequest(head, headers);
    }

    public CommonHttpResponse delete(String host, int port, String uri, Header... headers) throws Exception {
        HttpDelete delete = new HttpDelete(url(host, port, uri));
        return request(host, port, delete, headers);
//        return doHttpRequest(delete, headers);
    }

    /**
     * get, head, delete 等只有请求头的请求
     * @param host ip
     * @param port 端口
     * @param request 请求
     * @param headers 头
     * @return 响应
     * @throws Exception 异常
     */
    public CommonHttpResponse request(String host, int port, HttpRequest request, Header... headers) throws Exception {
        for (Header header : headers) {
            request.addHeader(header);
        }
        for (Header header : this.headers) {
            request.addHeader(header);
        }
        String _host = host + ":" + port;
        request.addHeader(new BasicHeader("Host", _host));
        request.addHeader(new BasicHeader("Content-Type", "application/json; charset=UTF-8"));
        HttpRoute route = new HttpRoute(new HttpHost(host, port));
        return doHttpRequest(request, route);
    }

//    public CommonHttpResponse post(String host, int port, String uri, String message, Header... headers) throws Exception{
//        HttpPost post = new HttpPost(url(host, port, uri));
//        return request(host, port, uri, METHOD_POST, post, message, headers);
//    }

    public CommonHttpResponse put(String host, int port, String uri, String message, Header... headers) throws Exception{
        HttpPut put = new HttpPut(url(host, port, uri));
        return request(host, port, uri, METHOD_PUT, put, message, headers);
    }

    public CommonHttpResponse patch(String host, int port, String uri, String message, Header... headers) throws Exception{
        HttpPatch patch = new HttpPatch(url(host, port, uri));
        return request(host, port, uri, METHOD_PATCH, patch, message, headers);
    }

    public CommonHttpResponse post(String host, int port, String uri, String message, Header... headers) throws Exception{
        HttpPost post = new HttpPost(url(host, port, uri));
        if(null != message && !"".equals(message)){
            post.setEntity(new StringEntity(message, ENCODE_CHARSET));
        }
        return doHttpRequest(post, headers);
    }
//
//    public HttpResponse put(String host, int port, String uri, String message, Header... headers) throws Exception{
//        HttpPut put = new HttpPut(url(host, port, uri));
//        if(null != message && !"".equals(message)){
//            put.setEntity(new StringEntity(message, ENCODE_CHARSET));
//        }
//        return doHttpRequest(put, headers);
//    }
//
//    public HttpResponse patch(String host, int port, String uri, String message, Header... headers) throws Exception{
//        HttpPatch patch = new HttpPatch(url(host, port, uri));
//        if(null != message && !"".equals(message)){
//            patch.setEntity(new StringEntity(message, ENCODE_CHARSET));
//        }
//        return doHttpRequest(patch, headers);
//    }

    /**
     * 当请求带有数据，如post, put, patch方法
     * @param host ip
     * @param port 端口
     * @param uri  请求的uri
     * @param message 提交的数据
     * @param headers 请求头。添加请求头
     * @return 响应
     * @throws Exception 这里异常比较多，后续再看是否对异常细分
     */
    public CommonHttpResponse request(String host, int port, String uri, String method, HttpRequest request, String message, Header... headers) throws Exception {
        HttpEntityEnclosingRequest entity = new BasicHttpEntityEnclosingRequest(method, uri);
        for (Header header : headers) {
            request.addHeader(header);
        }
        for (Header header : this.headers) {
            request.addHeader(header);
        }
        String _host = host + ":" + port;
        request.addHeader(new BasicHeader("Host", _host));
        request.addHeader(new BasicHeader("Content-Type", "application/json; charset=UTF-8"));
        if (null != message && !"".equals(message)) {
            byte[] bytes = message.getBytes();
            request.addHeader(new BasicHeader("Content-Length", String.valueOf(bytes.length)));
            HttpEntity body = new ByteArrayEntity(bytes, ContentType.APPLICATION_JSON);
            entity.setEntity(body);
        }else {
            request.addHeader(new BasicHeader("Content-Length", String.valueOf(0)));
        }
        HttpRoute route = new HttpRoute(new HttpHost(host, port));
        return doHttpRequest(request, entity, route);
    }

    public CommonHttpResponse put(String host, int port, String uri, File file, Header... headers) throws Exception {
        HttpPut put = new HttpPut(url(host, port, uri));
        if (null != file && file.isFile()) {
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.addBinaryBody("file", file);
            HttpEntity entity = builder.build();
            put.setEntity(entity);
        }
//        for (Header header : headers) {
//            put.addHeader(header);
//        }
//        HttpRoute route = new HttpRoute(new HttpHost(host, port));
        return doHttpRequest(put, headers);
    }

    private CommonHttpResponse doHttpRequest(HttpRequest request, HttpRoute route)
            throws InterruptedException, ExecutionException, IOException, HttpException {
        HttpClientConnection client = null;
        try{
            client = httpClient(route);
            System.out.println(client);
            client.sendRequestHeader(request);
            client.flush();
            return readResponse(client);
        }catch (InterruptedException | ExecutionException | IOException | HttpException e){
            throw e;
        }finally {
            if(null != client){
                connections.releaseConnection(client, null, 1, TimeUnit.MINUTES);
            }
            release();
        }
    }

    private CommonHttpResponse doHttpRequest(HttpUriRequest request, Header... headers)
            throws IOException, HttpException {
        for(Header header: headers){
            request.addHeader(header);
        }
        CloseableHttpClient client = closeableClient();
        CloseableHttpResponse response = client.execute(request);

        StatusLine statusLine = response.getStatusLine();
        if(null == statusLine){
            throw new HttpException("Http response error: no status line.");
        }
        int code = statusLine.getStatusCode();
        HttpEntity result = response.getEntity();
        byte[] bytes = EntityUtils.toByteArray(result);
        CommonHttpResponse data = new CommonHttpResponse(code, bytes);
        response.close();
        return data;
    }

    private CommonHttpResponse doHttpRequest(HttpRequest request, HttpEntityEnclosingRequest entity, HttpRoute route)
            throws InterruptedException, ExecutionException, IOException, HttpException {
        HttpClientConnection client = null;
        try{
            client = httpClient(route);
            client.sendRequestHeader(request);
            client.sendRequestEntity(entity);
            client.flush();
            return readResponse(client);
        }catch (InterruptedException | ExecutionException | IOException | HttpException e){
            throw e;
        }finally {
            if(null != client){
                connections.releaseConnection(client, null, 1, TimeUnit.MINUTES);
            }
            release();
        }
    }

    /**
     * 从连接中读取http响应
     * @param connection http连接
     * @return http响应
     * @throws IOException IOException
     * @throws HttpException HttpException
     */
    private CommonHttpResponse readResponse(HttpClientConnection connection) throws IOException, HttpException {
        if(connection.isResponseAvailable(60000)){
            HttpResponse response = connection.receiveResponseHeader();
            Header[] headers = response.getHeaders("Content-Length");
            if(headers.length > 0){
                Header header = headers[0];
                int length = Integer.valueOf(header.getValue());
                if(length > 0){
                    connection.receiveResponseEntity(response);
                }
            }
            StatusLine statusLine = response.getStatusLine();
            if(null == statusLine){
                throw new HttpException("Http response error: no status line.");
            }
            int code = statusLine.getStatusCode();
            HttpEntity result = response.getEntity();
            byte[] bytes = EntityUtils.toByteArray(result);
            return new CommonHttpResponse(code, bytes);
        }else {
            throw new HttpException("Read data from response error: time out");
        }
    }

    /**
     * 从连接池获取一个连接
     * @param route 路由
     * @return HttpClientConnection
     * @throws InterruptedException InterruptedException
     * @throws ExecutionException ExecutionException
     * @throws IOException IOException
     */
    private HttpClientConnection httpClient(HttpRoute route) throws InterruptedException, ExecutionException, IOException {
        ConnectionRequest connRequest = connections.requestConnection(route, null);
        try {
            HttpClientConnection conn = connRequest.get(10000, TimeUnit.SECONDS);
            if (!conn.isOpen()) {
                connections.connect(conn, route, 10000, context);
                connections.routeComplete(conn, route, context);
            }
            return conn;
        } catch (InterruptedException | ExecutionException | IOException e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 可以获取一个CloseableHttpClient
     * @return CloseableHttpClient
     */
    private CloseableHttpClient closeableClient() {
        // 请求重试处理
        HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> {
            if (executionCount >= 3) {// 如果已经重试了3次，就放弃
                return false;
            }
            if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                return true;
            }
            if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                return false;
            }
            if (exception instanceof InterruptedIOException) {// 超时
                return false;
            }
            if (exception instanceof UnknownHostException) {// 目标服务器不可达
                return false;
            }
            if (exception instanceof SSLException) {// ssl握手异常
                return false;
            }

            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            // 如果请求是幂等的，就再次尝试
            return !(request instanceof HttpEntityEnclosingRequest);
        };

        RequestConfig requestConfig = RequestConfig
                .custom()
                .setConnectionRequestTimeout(REQUEST_TIMEOUT)
                .setConnectTimeout(REQUEST_TIMEOUT)
                .setSocketTimeout(REQUEST_SOCKET_TIME)
                .build();

        // 设置重定向策略
        LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();

        CloseableHttpClient httpClient = HttpClients
                .custom()
                .setConnectionManager(connections)
                .setDefaultRequestConfig(requestConfig)
                .setRedirectStrategy(redirectStrategy)
                .setRetryHandler(httpRequestRetryHandler)
                .build();

        return httpClient;
    }

    private String url(String host, int port, String uri){
        return "http://" + host + ":" + port + uri;
    }

    private void release(){
        long now = System.currentTimeMillis();
        long internal = now - recent;
        if(internal > CONNECTION_CHECK_INTERNAL){
            connections.closeExpiredConnections();
            recent = now;
        }
    }

    public byte[] readFromResponse(HttpResponse response) throws IOException{
        HttpEntity result = response.getEntity();

        return EntityUtils.toByteArray(result);
    }
}
